<?php declare(strict_types=1);
/**
 * @author      xianganyall <xianganyall@gmail.com>
 * @copyright   2023-2025 owner
 **/

namespace Srv\Libs\Frame;

use Exception;
use Srv\Libs\Common\CommNet;
use Srv\Libs\Common\CommValid;

final class CheckLimit
{
    public const LIMIT_TYPE_DEVID               = 'DEVID';              // 设备ID
    public const LIMIT_TYPE_USER                = 'USER';               // 用户ID
    public const LIMIT_TYPE_IP                  = 'IP';                 // IP地址
    public const LIMIT_TYPE_IPV4_1              = 'IPV4_1';             // IPV4_A段
    public const LIMIT_TYPE_IPV4_2              = 'IPV4_2';             // IPV4_B段
    public const LIMIT_TYPE_IPV4_3              = 'IPV4_3';             // IPV4_C段
    public const LIMIT_TYPE_IPV6_1              = 'IPV6_1';             // IPV6_A段
    public const LIMIT_TYPE_IPV6_2              = 'IPV6_2';             // IPV6_B段
    public const LIMIT_TYPE_IPV6_3              = 'IPV6_3';             // IPV6_C段
    public const LIMIT_TYPE_IPV6_4              = 'IPV6_4';             // IPV6_D段
    public const LIMIT_TYPE_IPV6_5              = 'IPV6_5';             // IPV6_E段
    public const LIMIT_TYPE_IPV6_6              = 'IPV6_6';             // IPV6_F段
    public const LIMIT_TYPE_IPV6_7              = 'IPV6_7';             // IPV6_G段
    public const LIMIT_TYPE_PATH                = 'PATH';               // 路径URL级
    public const LIMIT_TYPE_ACT                 = 'ACT';                // Action级

    public const LIMIT_TIME_SECOND              = 's';                  // 时间[秒]
    public const LIMIT_TIME_MINUTE              = 'm';                  // 时间[分]
    public const LIMIT_TIME_HOUR                = 'h';                  // 时间[时]
    public const LIMIT_TIME_DAY                 = 'd';                  // 时间[天]

    private static string $urlPath              = '';
    private static string $actStr               = '';
    private static string $devId                = '';
    private static int $userId                  = 0;
    private static string $currIpStr            = '';
    private static string $currIpV4             = '';
    private static int $currIpV4Int             = 0;
    private static string $currIpV6Fill         = '';
    private static bool $isIpV4                 = false;
    private static bool $isIpv6                 = false;

    /**
     * @return void
     * init
     */
    public static function init(string $urlPath, string $actStr):void
    {
        self::$urlPath          = $urlPath;
        self::$actStr           = $actStr;
        self::$devId            = RequestHeader::getDevId();
        self::$userId           = AuthData::getLoginUserId();
        self::$currIpStr        = CommNet::getReqIp([], CommNet::REQ_FORWARD_FOR_TYPE_BEFORE, true);
        self::$currIpV4         = '';
        self::$currIpV4Int      = 0;
        self::$currIpV6Fill     = '';
        self::$isIpV4           = CommValid::isIp(self::$currIpStr);
        self::$isIpv6           = !self::$isIpV4 && CommValid::isIpv6(self::$currIpStr);
        if(self::$isIpV4){
            self::$currIpV4     = self::$currIpStr;
            self::$currIpV4Int  = CommNet::ipToLong(self::$currIpV4);
        }else if(self::$isIpv6){
            self::$currIpV6Fill = CommNet::ipv6Fill(self::$currIpStr);
        }
    }

    /**
     * @param string $limitType
     * @param string $limitVal
     * @param string $limitBreak
     * @param string $limitSpeed
     * @param string $limitSrc
     * @param array $limitConf
     * @return bool
     * parseConf
     */
    public static function parseConf(string $limitType, string $limitVal, string $limitBreak, string $limitSpeed, string $limitSrc, array &$limitConf):bool
    {
        if(!in_array($limitType, [self::LIMIT_TYPE_DEVID, self::LIMIT_TYPE_USER, self::LIMIT_TYPE_IP, self::LIMIT_TYPE_IPV4_1, self::LIMIT_TYPE_IPV4_2, self::LIMIT_TYPE_IPV4_3, self::LIMIT_TYPE_IPV6_1, self::LIMIT_TYPE_IPV6_2, self::LIMIT_TYPE_IPV6_3, self::LIMIT_TYPE_IPV6_4, self::LIMIT_TYPE_IPV6_5, self::LIMIT_TYPE_IPV6_6, self::LIMIT_TYPE_IPV6_7, self::LIMIT_TYPE_PATH, self::LIMIT_TYPE_ACT], true)) return false;
        $isBreak                = $limitBreak === '$';
        $limitValLength         = strlen($limitVal);
        if($limitValLength !== 0 && $limitValLength < 3) return false;
        $limitSpeed             = trim($limitSpeed);
        if(strlen($limitSpeed) < 1) return false;
        $isUnlimited            = false;
        $speedList              = [];
        $speedStr               = '';
        if($limitSpeed === '-'){
            $isUnlimited        = true;
            $speedStr           = '-';
        }else{
            foreach (explode(',', $limitSpeed) as $speedVal){
                if(preg_match('/^(\d+)\/(\d+)?([smhd])$/', trim($speedVal), $matchSpeed)){
                    $speedNum       = intval($matchSpeed[1]);
                    $speedTNum      = max(intval($matchSpeed[2]), 1);
                    $speedTUnit     = strtolower($matchSpeed[3]);
                    $speedTime      = 0;
                    switch ($speedTUnit){
                        case self::LIMIT_TIME_SECOND:{  $speedTime  = $speedTNum*1; break; }
                        case self::LIMIT_TIME_MINUTE:{  $speedTime  = $speedTNum*60; break; }
                        case self::LIMIT_TIME_HOUR:{    $speedTime  = $speedTNum*3600; break; }
                        case self::LIMIT_TIME_DAY:{     $speedTime  = $speedTNum*86400; break; }
                    }
                    if($speedTime < 1 || $speedNum < 0 || $speedTime > 2592000) return false;
                    $speedStr       .= (strlen($speedStr) > 0 ? ', ' : '').$speedNum.'/'.($speedTNum > 1 ? $speedTNum : '').$speedTUnit;
                    $speedList[]    = [$speedNum, $speedTime];
                }else{
                    return false;
                }
            }
        }
        $limitValList               = [];
        $limitValSrcList            = $limitValLength !== 0 ? explode(',', substr($limitVal, 1, -1)) : [];
        switch ($limitType){
            case self::LIMIT_TYPE_DEVID:{
                foreach ($limitValSrcList as $limitValSrc){
                    $limitValSrc            = trim($limitValSrc);
                    $limitValSrcLength      = strlen($limitValSrc);
                    if($limitValSrcLength < 1) return false;
                    if($limitValSrcLength === 36 && preg_match('/^[A-Z\d]{8}-[A-Z\d]{4}-[A-Z\d]{4}-[A-Z\d]{4}-[A-Z\d]{12}$/', $limitValSrc)){
                        $limitValList[]     = strtoupper($limitValSrc);
                    }else{
                        $isExistsKey        = false;
                        $limitTmpList       = self::getDevIdConfList($limitValSrc, $isExistsKey);
                        if(!$isExistsKey) return false;
                        if(count($limitTmpList) > 0) $limitValList = array_merge($limitValList, $limitTmpList);
                    }
                }
                break;
            }
            case self::LIMIT_TYPE_USER:{
                foreach ($limitValSrcList as $limitValSrc){
                    $limitValSrc            = trim($limitValSrc);
                    $limitValSrcLength      = strlen($limitValSrc);
                    if($limitValSrcLength < 1) return false;
                    if($limitValSrcLength <= 16 && preg_match('/^\d{1,16}$/', $limitValSrc)){
                        $limitTmpVal        = intval($limitValSrc);
                        if($limitTmpVal < 1) return false;
                        $limitValList[]     = $limitTmpVal;
                    }else{
                        $isExistsKey        = false;
                        $limitTmpList       = self::getUserIdConfList($limitValSrc, $isExistsKey);
                        if(!$isExistsKey) return false;
                        if(count($limitTmpList) > 0) $limitValList = array_merge($limitValList, $limitTmpList);
                    }
                }
                break;
            }
            case self::LIMIT_TYPE_IP:{
                foreach ($limitValSrcList as $limitValSrc){
                    $limitValSrc            = trim($limitValSrc);
                    $limitValSrcLength      = strlen($limitValSrc);
                    if($limitValSrcLength < 1) return false;
                    if(CommValid::isIp($limitValSrc)){
                        $limitValList[]     = $limitValSrc;
                    }else if(preg_match('/(.*)-(\d+)/', $limitValSrc, $match)){
                        if(!CommValid::isIp($match[1])) return false;
                        $stopIpInt          = intval($match[2]);
                        if($stopIpInt < 1 || $stopIpInt > 255) return false;
                        $endIpPos           = strrpos($match[1], '.');
                        $endIpInt           = intval(substr($match[1], $endIpPos+1));
                        if($endIpInt >= $stopIpInt) return false;
                        $ipIntRange         = [CommNet::ipToLong($match[1]), CommNet::ipToLong(substr($match[1], 0, $endIpPos+1).$stopIpInt)];
                        if($ipIntRange[0] === 0 && $ipIntRange[1] === 0) return false;
                        $limitValList[]     = $ipIntRange;
                    }else if(preg_match('/(.*)\/(\d+)/', $limitValSrc, $match)){
                        $ipIntRange         = CommNet::cidrToIpInt($limitValSrc);
                        if($ipIntRange[0] === 0 && $ipIntRange[1] === 0) return false;
                        $limitValList[]     = $ipIntRange;
                    }else if(CommValid::isIpv6($limitValSrc)){
                        $limitValList[]     = CommNet::ipv6Fill($limitValSrc);
                    }else{
                        $isExistsKey        = false;
                        $limitTmpList       = self::getIpConfList($limitValSrc, $isExistsKey);
                        if(!$isExistsKey) return false;
                        if(count($limitTmpList) > 0) $limitValList = array_merge($limitValList, $limitTmpList);
                    }
                }
                break;
            }
        }
        $limitValListSize   = count($limitValList);
        if($isUnlimited && $limitValListSize < 1) return false;     // 无限且无指定对象值规则
        if($limitValListSize > 0) $limitValList = array_unique($limitValList);
        $limitConf          = [$limitType, $limitValList, $isBreak, $isUnlimited, $speedList, $limitSrc, $speedStr];
        return true;
    }

    /**
     * @param string $devIdConfKey
     * @param bool $isExistsKey
     * @return array
     * getDevIdConfList
     */
    public static function getDevIdConfList(string $devIdConfKey, bool &$isExistsKey):array
    {
        $isExistsKey = false;
        unset($devIdConfKey);
        // todo-hx 读取设备Id配置集合
        return [];
    }

    /**
     * @param string $userIdConfKey
     * @param bool $isExistsKey
     * @return array
     * getUserIdConfList
     */
    public static function getUserIdConfList(string $userIdConfKey, bool &$isExistsKey):array
    {
        $isExistsKey = false;
        unset($userIdConfKey);
        // todo-hx 读取用户Id配置集合
        return [];
    }

    /**
     * @param string $ipConfKey
     * @param bool $isExistsKey
     * @return array
     * getIpConfList
     */
    public static function getIpConfList(string $ipConfKey, bool &$isExistsKey):array
    {
        $isExistsKey = false;
        unset($ipConfKey);
        // todo-hx 读取IP配置集合
        return [];
    }

    /**
     * @param string $limitType
     * @param array $limitValList
     * @param bool $isUnlimited
     * @param array $speedList
     * @param string $limitSrc
     * @param bool $isBreak
     * @return bool
     * checkSpeed
     */
    public static function checkSpeed(string $limitType, array $limitValList, bool $isUnlimited, array $speedList, string $limitSrc, bool &$isBreak):bool
    {
        $limitValListSize   = count($limitValList);
        $speedKey           = '';
        switch ($limitType){
            case self::LIMIT_TYPE_DEVID:{
                if(strlen(self::$devId) < 1) return true;
                if($limitValListSize > 0 && !in_array(self::$devId, $limitValList, true)){    // 非目标对象
                    if($isBreak) $isBreak = false;  // 非目标对象,不结束
                    return true;
                }
                $speedKey           = $limitType.self::$devId;
                break;
            }
            case self::LIMIT_TYPE_USER:{
                if(self::$userId < 1) return true;
                if($limitValListSize > 0 && !in_array(self::$userId, $limitValList, true)){   // 非目标对象
                    if($isBreak) $isBreak = false;  // 非目标对象,不结束
                    return true;
                }
                $speedKey           = $limitType.self::$userId;
                break;
            }
            case self::LIMIT_TYPE_IP:{
                if(!self::$isIpV4 && !self::$isIpv6) return true;
                $currIpStr          = self::$isIpV4 ? self::$currIpV4 : (self::$isIpv6 ? self::$currIpV6Fill : '');
                if($limitValListSize > 0){
                    $matchResult    = false;
                    foreach ($limitValList as $limitValItem){
                        if(is_array($limitValItem)){
                            if(self::$currIpV4Int > 0 && $limitValItem[0] <= self::$currIpV4Int && self::$currIpV4Int <= $limitValItem[1]){
                                $matchResult    = true;
                                break;
                            }
                        }else{
                            if($limitValItem === $currIpStr){
                                $matchResult    = true;
                                break;
                            }
                        }
                    }
                    if(!$matchResult){  // 非目标对象
                        if($isBreak) $isBreak = false;  // 非目标对象,不结束
                        return true;
                    }
                }
                $speedKey           = $limitType.$currIpStr;
                break;
            }
            case self::LIMIT_TYPE_IPV4_1:{
                if(!self::$isIpV4) return true;
                list($currIpA)      = explode('.', self::$currIpV4);
                $currIpStr          = $currIpA.'.0.0.0';
                $speedKey           = $limitType.$currIpStr;
                break;
            }
            case self::LIMIT_TYPE_IPV4_2:{
                if(!self::$isIpV4) return true;
                list($currIpA, $currIpB) = explode('.', self::$currIpV4);
                $currIpStr          = $currIpA.'.'.$currIpB.'.0.0';
                $speedKey           = $limitType.$currIpStr;
                break;
            }
            case self::LIMIT_TYPE_IPV4_3:{
                if(!self::$isIpV4) return true;
                $currIpStr          = substr(self::$currIpV4, 0, strrpos(self::$currIpV4, '.')).'.0';
                $speedKey           = $limitType.$currIpStr;
                break;
            }
            case self::LIMIT_TYPE_IPV6_1:{
                if(!self::$isIpv6) return true;
                list($currIpA)      = explode(':', self::$currIpV6Fill);
                $currIpStr          = $currIpA.':::::::';
                $speedKey           = $limitType.$currIpStr;
                break;
            }
            case self::LIMIT_TYPE_IPV6_2:{
                if(!self::$isIpv6) return true;
                list($currIpA, $currIpB) = explode(':', self::$currIpV6Fill);
                $currIpStr          = $currIpA.':'.$currIpB.'::::::';
                $speedKey           = $limitType.$currIpStr;
                break;
            }
            case self::LIMIT_TYPE_IPV6_3:{
                if(!self::$isIpv6) return true;
                list($currIpA, $currIpB, $currIpC) = explode(':', self::$currIpV6Fill);
                $currIpStr          = $currIpA.':'.$currIpB.':'.$currIpC.':::::';
                $speedKey           = $limitType.$currIpStr;
                break;
            }
            case self::LIMIT_TYPE_IPV6_4:{
                if(!self::$isIpv6) return true;
                list($currIpA, $currIpB, $currIpC, $currIpD) = explode(':', self::$currIpV6Fill);
                $currIpStr          = $currIpA.':'.$currIpB.':'.$currIpC.':'.$currIpD.'::::';
                $speedKey           = $limitType.$currIpStr;
                break;
            }
            case self::LIMIT_TYPE_IPV6_5:{
                if(!self::$isIpv6) return true;
                list($currIpA, $currIpB, $currIpC, $currIpD, $currIpE) = explode(':', self::$currIpV6Fill);
                $currIpStr          = $currIpA.':'.$currIpB.':'.$currIpC.':'.$currIpD.':'.$currIpE.':::';
                $speedKey           = $limitType.$currIpStr;
                break;
            }
            case self::LIMIT_TYPE_IPV6_6:{
                if(!self::$isIpv6) return true;
                list($currIpA, $currIpB, $currIpC, $currIpD, $currIpE, $currIpF) = explode(':', self::$currIpV6Fill);
                $currIpStr          = $currIpA.':'.$currIpB.':'.$currIpC.':'.$currIpD.':'.$currIpE.':'.$currIpF.'::';
                $speedKey           = $limitType.$currIpStr;
                break;
            }
            case self::LIMIT_TYPE_IPV6_7:{
                if(!self::$isIpv6) return true;
                list($currIpA, $currIpB, $currIpC, $currIpD, $currIpE, $currIpF, $currIpG) = explode(':', self::$currIpV6Fill);
                $currIpStr          = $currIpA.':'.$currIpB.':'.$currIpC.':'.$currIpD.':'.$currIpE.':'.$currIpF.':'.$currIpG.':';
                $speedKey           = $limitType.$currIpStr;
                break;
            }
            case self::LIMIT_TYPE_PATH:{
                if(strlen(self::$urlPath) < 1) return true;
                $speedKey           = $limitType.self::$urlPath;
                break;
            }
            case self::LIMIT_TYPE_ACT:{
                if(strlen(self::$actStr) < 1) return true;
                $speedKey           = $limitType.self::$actStr;
                break;
            }
        }
        if($isUnlimited) return true;       // 不限制
        if(count($speedList) > 0) foreach ($speedList as $speedItem){
            $speedNum       = $speedItem[0];
            $speedTime      = $speedItem[1];
            $remaining      = 0;
            if(!self::checkSpeedNum($speedKey, $speedNum, $speedTime, $remaining)) return false;
        }
        return true;
    }

    /**
     * @param string $speedKey
     * @param int $speedNum
     * @param int $speedTime
     * @param int $remaining
     * @return bool
     * checkSpeedNum
     */
    private static function checkSpeedNum(string $speedKey, int $speedNum, int $speedTime, int &$remaining):bool
    {
        if(strlen($speedKey) < 1 || $speedNum <= 0 || $speedTime < 1) return false;
        $cacheKey       = 'LIMIT_'.$speedTime.'_'.md5($speedTime.'_'.$speedKey);
        try{
            $DataRedis  = ControllerAbstract::getCacheAuto();
            $resultNum  = $DataRedis->inc($cacheKey);
            if($resultNum < 1) return false;
            if($resultNum === 1) $DataRedis->setExpire($cacheKey, $speedTime);
            $remaining  = max($speedNum - $resultNum, 0);
            return $resultNum <= $speedNum;
        }catch(Exception $Exception){
            return false;
        }
    }
}